//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <eladef.h>
#include <ddk.h>
#include "pci.h"

#ifdef _MSC_VER
#pragma warning(disable:4244)
#pragma warning(disable:4761)
#endif

pciConfigPtr pci_devp[MAX_PCI_DEVICES + 1] = {NULL, };
PciProbeType PCIFlags = PCIProbe1;

int pciConfigType = 0;
int pciMaxDevice = 0;

static Boolean
pcibusCheck()
{
    UInt8 device;

    for (device = 0; device < pciMaxDevice; device++) {
        UInt32 id;
        id = pciReadLong(pcibusTag(0, device, 0), 0);
        if (id && id != 0xffffffff) {
            return TRUE;
        }
    }
    return 0;
}

static void
pcibusSetup()
{
    static Boolean setupDone = FALSE;
    UInt32 mode1Res1 = 0, mode1Res2 = 0, oldVal1 = 0;
    UInt8  mode2Res1 = 0, mode2Res2 = 0, oldVal2 = 0;
    int stages = 0;

    if (setupDone)
        return;

    setupDone = TRUE;

    switch (PCIFlags) {

        case PCIProbe1:
            oldVal1 = IOSpaceReadInt32(PCI_MODE1_ADDRESS_REG);

            if ((oldVal1 & 0x7f000000) == 0) {
                stages |= 0x01;

                pciConfigType = 1;
                pciMaxDevice = PCI_CONFIG1_MAXDEV;

                IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, PCI_EN | oldVal1);

                mode1Res1 = IOSpaceReadInt32(PCI_MODE1_ADDRESS_REG);

                if (mode1Res1) {

                    stages |= 0x02;

                    if (pcibusCheck()) {
                        return;
                    }
                }

                stages |= 0x04;

                IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, 0xff000001);
                mode1Res2 = IOSpaceReadInt32(PCI_MODE1_ADDRESS_REG);

                IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, oldVal1);

                if ((mode1Res2 & 0x80000001) == 0x80000000) {

                    stages |= 0x08;

                    if (pcibusCheck()) {
                        return;
                    }
                }
            }

            /* Try config type 2 */
            oldVal2 = IOSpaceReadInt8(PCI_MODE2_ENABLE_REG);

            if ((oldVal2 & 0xf0) == 0) {
                pciConfigType = 2;
                pciMaxDevice = PCI_CONFIG2_MAXDEV;

                IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, 0x0e);
                mode2Res1 = IOSpaceReadInt8(PCI_MODE2_ENABLE_REG);
                IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, oldVal2);

                if (mode2Res1 == 0x0e) {
                    if (pcibusCheck()) {
                        return;
                    }
                }
            }
            break;

        case PCIProbe2:

            /* The scanpci-style detection method */

            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, 0x00);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, 0x00);
            mode2Res1 = IOSpaceReadInt8(PCI_MODE2_ENABLE_REG);
            mode2Res2 = IOSpaceReadInt8(PCI_MODE2_FORWARD_REG);

            if (mode2Res1 == 0 && mode2Res2 == 0) {
                pciConfigType = 2;
                pciMaxDevice = PCI_CONFIG2_MAXDEV;
                return;
            }

            oldVal1 = IOSpaceReadInt32(PCI_MODE1_ADDRESS_REG);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, PCI_EN);
            mode1Res1 = IOSpaceReadInt32(PCI_MODE1_ADDRESS_REG);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, oldVal1);
            if (mode1Res1 == PCI_EN) {
                pciConfigType = 1;
                pciMaxDevice = PCI_CONFIG1_MAXDEV;
                return;
            }
            break;

        case PCIForceConfig1:
            pciConfigType = 1;
            pciMaxDevice = PCI_CONFIG1_MAXDEV;
            return;

        case PCIForceConfig2:
            pciConfigType = 2;
            pciMaxDevice = PCI_CONFIG2_MAXDEV;
            return;
    }

    /* No PCI found */

    pciConfigType = 0;
    pciMaxDevice = 0;
}

pciTagRec
pcibusTag(UInt8 bus, UInt8 cardnum, UInt8 func)
{
    pciTagRec tag;

    tag.cfg1 = 0;

    if (func > 7)
        return tag;

    switch (pciConfigType) {
        case 1:
            if (cardnum < PCI_CONFIG1_MAXDEV) {
                tag.cfg1 =
                PCI_EN |((UInt32)bus << 16) |
                ((UInt32)cardnum << 11) |
                ((UInt32)func << 8);
            }
            break;
        case 2:
            if (cardnum < PCI_CONFIG2_MAXDEV) {
                tag.cfg2.port    = 0xc000 | ((UInt16)cardnum << 8);
                tag.cfg2.enable  = 0xf0 | (func << 1);
                tag.cfg2.forward = bus;
            }
            break;
    }
    return tag;
}

static pciTagRec
pcibusFTag(pciTagRec tag, UInt8 func)
{
    if (func > 7) {
        return tag;
    }

    switch (pciConfigType) {
        case 1:
            tag.cfg1 &= ~0x700UL;
            tag.cfg1 |= ((UInt32)func << 8);
            break;
        case 2:
            tag.cfg2.enable = 0xf0 | (func << 1);
            break;
    }
    return tag;
}

UInt32
pciReadLong(pciTagRec tag, UInt32 reg)
{
    UInt32 addr, data = 0;

    if (!tag.cfg1) {
        return 0xffffffff;
    }

    switch (pciConfigType) {
        case 1:
            addr = tag.cfg1 | (reg & 0xfc);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, addr);
            data = IOSpaceReadInt32(PCI_MODE1_DATA_REG);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, 0);
            break;
        case 2:
            addr = tag.cfg2.port | (reg & 0xfc);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
            data = IOSpaceReadInt32((UInt16)addr);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, 0);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, 0);
            break;
    }

    return data;
}

UInt16
pciReadWord(pciTagRec tag, UInt32 reg)
{
    UInt32 addr;
    UInt16 data = 0;

    if (!tag.cfg1) {
        return 0xff;
    }

    switch (pciConfigType) {
        case 1:
            addr = tag.cfg1 | (reg & 0xfc);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, addr);
            data = IOSpaceReadInt16(PCI_MODE1_DATA_REG);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, 0);
            break;
        case 2:
            addr = tag.cfg2.port | (reg & 0xfc);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
            data = IOSpaceReadInt16((UInt16)addr);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, 0);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, 0);
            break;
    }

    return data;
}

UInt8
pciReadByte(pciTagRec tag, UInt32 reg)
{
    UInt32 addr;
    UInt8 data = 0;

    if (!tag.cfg1) {
        return 0xff;
    }

    switch (pciConfigType) {
        case 1:
            addr = tag.cfg1 | (reg & 0xfc);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, addr);
            data = IOSpaceReadInt8(PCI_MODE1_DATA_REG);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, 0);
            break;
        case 2:
            addr = tag.cfg2.port | (reg & 0xfc);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
            data = IOSpaceReadInt8((UInt16)addr);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, 0);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, 0);
            break;
    }

    return data;
}

void
pcibusWrite(pciTagRec tag, UInt32 reg, UInt32 data)
{
    UInt32 addr;

    if (!tag.cfg1) {
        return;
    }

    switch (pciConfigType) {
        case 1:
            addr = tag.cfg1 | (reg & 0xfc);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, addr);
            IOSpaceWriteInt32(PCI_MODE1_DATA_REG, data);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, 0);
            break;
        case 2:
            addr = tag.cfg2.port | (reg & 0xfc);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
            IOSpaceWriteInt32((UInt16)addr, data);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, 0);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, 0);
            break;
    }
}

void
pciWriteWord(pciTagRec tag, UInt32 reg, UInt16 data)
{
    UInt32 addr;

    if (!tag.cfg1) {
        return;
    }

    switch (pciConfigType) {
        case 1:
            addr = tag.cfg1 | (reg & 0xfc);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, addr);
            IOSpaceWriteInt16(PCI_MODE1_DATA_REG, data);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, 0);
            break;

        case 2:
            addr = tag.cfg2.port | (reg & 0xfc);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
            IOSpaceWriteInt16((UInt16)addr, data);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, 0);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, 0);
            break;
    }
}

void
pciWriteByte(pciTagRec tag, UInt32 reg, UInt8 data)
{
    UInt32 addr;

    if (!tag.cfg1) {
        return;
    }

    switch (pciConfigType) {
        case 1:
            addr = tag.cfg1 | (reg & 0xfc);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, addr);
            IOSpaceWriteInt8(PCI_MODE1_DATA_REG, data);
            IOSpaceWriteInt32(PCI_MODE1_ADDRESS_REG, 0);
            break;
        case 2:
            addr = tag.cfg2.port | (reg & 0xfc);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
            IOSpaceWriteInt8((UInt16)addr, data);
            IOSpaceWriteInt8(PCI_MODE2_ENABLE_REG, 0);
            IOSpaceWriteInt8(PCI_MODE2_FORWARD_REG, 0);
            break;
    }
}

static void
pciEnableIO(int scrnIndex)
{
}

static void
pciDisableIO(int scrnIndex)
{
}

static Boolean
pciMfDev(UInt8 bus, UInt8 cardnum)
{
    pciTagRec tag0, tag1;
    UInt32 id0, id1;

    /* Detect a multi-function device that complies to the PCI 2.0 spec */

    tag0 = pcibusTag(bus, cardnum, 0);
    if (pciReadLong(tag0, PCI_HEADER_MISC) & PCI_HEADER_MULTIFUNCTION)
        return TRUE;

    /*
     * Now, to find non-compliant devices...
     * If there is a valid ID for function 1 and the ID for func 0 and 1
     * are different, or the base0 values of func 0 and 1 are differend,
     * then assume there is a multi-function device.
     */
    tag1 = pcibusTag(bus, cardnum, 1);
    id1 = pciReadLong(tag1, PCI_ID_REG);
    if (id1 != 0xffffffff) {
        id0 = pciReadLong(tag0, PCI_ID_REG);
        if (id0 != id1)
            return TRUE;
        if (pciReadLong(tag0, PCI_MAP_REG_START)
            != pciReadLong(tag1, PCI_MAP_REG_START))
            return TRUE;
    }
    return FALSE;
}

pciConfigPtr *
scanpci(int scrnIndex)
{
    pciConfigRec pcr;
    int pcibusidx, pcinumbus, pcibuses[16];
    int idx = 0;

    if (pci_devp[0])
        return pci_devp;

    pciEnableIO(scrnIndex);

    /* Check for a PCI bus, and find the config type */
    pcibusSetup();

    if (pciConfigType == 0)
        return (pciConfigPtr *)NULL;

    pcibusidx = 0;
    pcibuses[0] = 0;
    pcinumbus = 1;

    do {
        for (pcr._cardnum = 0; pcr._cardnum <
            (unsigned)pciMaxDevice; pcr._cardnum++) {
            int maxfunc = 0;
            pciTagRec tag;

            pcr._bus = pcibuses[pcibusidx];

            if (pciMfDev(pcr._bus, pcr._cardnum))
                maxfunc = 7;

            tag = pcibusTag(pcr._bus, pcr._cardnum, 0);
            for (pcr._func = 0; pcr._func <= (unsigned int)maxfunc;
                pcr._func++) {
                tag = pcibusFTag(tag, pcr._func);
                pcr._device_vendor = pciReadLong(tag, PCI_ID_REG);
                if (pcr._device_vendor == 0xffffffff) /* nothing there */
                    continue;

                pcr._status_command = pciReadLong(tag, PCI_CMD_STAT_REG);
                pcr._class_revision = pciReadLong(tag, PCI_CLASS_REG);
                pcr._bist_header_latency_cache =
                    pciReadLong(tag, PCI_HEADER_MISC);
                pcr._base0 = pciReadLong(tag, PCI_MAP_REG_START);
                pcr._base1 = pciReadLong(tag, PCI_MAP_REG_START + 0x04);
                pcr._base2 = pciReadLong(tag, PCI_MAP_REG_START + 0x08);
                pcr._base3 = pciReadLong(tag, PCI_MAP_REG_START + 0x0c);
                pcr._base4 = pciReadLong(tag, PCI_MAP_REG_START + 0x10);
                pcr._base5 = pciReadLong(tag, PCI_MAP_REG_START + 0x14);
                pcr.rsvd1  = pciReadLong(tag, PCI_MAP_REG_START + 0x18);
                pcr.rsvd2  = pciReadLong(tag, PCI_MAP_REG_START + 0x1C);
                pcr._baserom = pciReadLong(tag, PCI_MAP_ROM_REG);
                pcr._max_min_ipin_iline = pciReadLong(tag, PCI_INTERRUPT_REG);
                pcr._user_config = pciReadLong(tag, PCI_REG_USERCONFIG);

                /* Check for PCI-PCI bridges */
                if (pcr._base_class == PCI_CLASS_BRIDGE &&
                    pcr._sub_class == PCI_SUBCLASS_BRIDGE_PCI) {
                    if (pcr._secondary_bus_number > 0) {
                        pcibuses[pcinumbus++] = pcr._secondary_bus_number;
                    }
                }

                if (idx >= MAX_PCI_DEVICES)
                    continue;

                if ((pci_devp[idx] = (pciConfigPtr)malloc(sizeof(pciConfigRec)))
                    == (pciConfigPtr)NULL) {
                    pciDisableIO(scrnIndex);
                    return (pciConfigPtr *)NULL;
                }

                memcpy(pci_devp[idx++], &pcr, sizeof(pciConfigRec));
                pci_devp[idx] = (pciConfigPtr)NULL;
            }
        }
    } while (++pcibusidx < pcinumbus);

    pciDisableIO(scrnIndex);
    return pci_devp;
}

void
writepci(int scrnIndex, int bus, int cardnum, int func, int reg,
    UInt32 mask, UInt32 value)
{
    pciTagRec tag;
    UInt32 data;

    pciEnableIO(scrnIndex);

    /* Check for a PCI bus, and find the config type */
    pcibusSetup();

    if (pciConfigType == 0)
        return;

    tag = pcibusTag(bus, cardnum, func);
    data = pciReadLong(tag, reg) & ~mask | (value & mask);
    pcibusWrite(tag, reg, data);

    pciDisableIO(scrnIndex);
}

void
cleanpci()
{
    int idx = 0;

    while (pci_devp[idx])
        free((void *)pci_devp[idx++]);

    pci_devp[0] = (pciConfigPtr)NULL;
}
